home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
LOGIC Apps
/
Logic-APPLE_II_APPS.iso
/
mac
/
LOGIC Apple II 5.25" Library - ProDOS
/
PRO045.dsk
/
INSTR.816OPS.txt
< prev
next >
Wrap
Text File
|
2012-02-16
|
16KB
|
378 lines
from: Brian Fitzgerald
HAL Labs
18942 Dallas
Perris, CA 92370
(c) 1987 HAL Labs
This document may be freely distributed as long as this copyright notice
goes with it!
The Surgeon General has not determined that the following material is dangerous
to your health. However...
Chapter 4: 65816 instructions and LISA syntax
A difference in syntax.
The accepted definition of 65816 instruction addressing modes is a bit awkward.
There are a few differences between what some users may be accustomed to as
regards the syntax of some instructions, and the way that LISA does things.
When the 6502 was designed, it was as a 8-bit processor; all the instructions
work with 8-bit data through the registers (A,X and Y) and 8-bit (zero page
and stack) and 16-bit (absolute) addresses. Since data size was smaller than
address size (for absolute addresses), addresses had to be manipulated in two
parts, low byte and high byte. So, if you had to initialize a zero page
pointer to some immediate-value address, there had to be some way to tell the
assembler to use the low and high order bytes of an address. In LISA, you would
use:
SCREEN equ $2000 ;start of //e hi-res screen
SCINIT lda #SCREEN ;get low byte of address
sta PTR
lda /SCREEN ;get high byte of address
sta PTR+1
So, here we use the '#' pound sign to specify the low byte of an immediate
value, and the '/' slash sign to specify the high byte of an immediate value.
(Some assemblers use #< and #> for low and high bytes).
Okay, so no problem, right?
Well, after a while (about 1981), 16-bit processors started to become very
popular, and certain people started thinking about a 16-bit version of the 6502.
So, now we have the 65816. The 65816 is unusual in that it directly executes
the older 6502 programs directly, with no translation; and yet it is also a
16-bit part. It handles this "split-personality" with the aid of several
internal flags that the programmer can manipulate. If the M
(for memory/accumulator) flag is set (M=1) then the accumulator (and all memory
fetch/stores) are 8-bit; if it is cleared (M=0) then we have a 16-bit
accumulator.
This can be confusing!! There are actually no 8 or 16-bit instructions per se;
if you are in 16-bit mode, all the appropriate instructions are 16-bit. So,
for example:
sep #%00100000 ;set for 8-bit memory/acc
lda #$1234 ;load 1-byte value #$34
rep #%00100000 ;set for 16-bit memory/acc
lda #$1234 ;load 2-byte value #$1234
The first line sets 8-bit memory/accumulator mode; therefore, the next LDA
instruction will load a 1-byte value into the accumulator. The third line
sets 16-bit memory/accumulator mode; therefore, the last LDA instruction will
load a 2-byte value into the accumulator. But, if you looked at the opcodes
generated, both LDAs are the same! The 65816 chip knows what is 8-bit and what
is 16-bit because it looks at the flags; but you have to remember.
Finally, the point of this; when you are writing code, you would like to be
able to look at a line of source code and say "Yes, I meant this to be 8-bit
(or 16-bit) blah blah blah". So, we need some further identifiers; and this
is where LISA really diverges from the standard. For 8-bit data we use:
lda #$123456 ;get low byte $56
lda /$123456 ;get mid byte $34
lda ^$123456 ;get bank byte $12
for 8-bit immediate data and
lda |$123456 ;get low word $3456
lda \$123456 ;get high word $0012
for 16-bit immediate data. The standard would have you use the exact same
modifiers for both 8-bit and 16-bit mode! Further, you have to go
through contortions to tell the assembler that you are in 8-bit or 16-bit mode.
This would not be that bad, except that you can't just stay in 16-bit mode;
if you want to do 8-bit manipulations (say, on character data), you have to
go to 8-bit mode.
According to programmers at Apple, 80% of their problems involved being in the
wrong mode!
Now, if you like that sort of thing (or if you are translating code from APW,
for example), you can use semi-standard mnemonics, in that '#' and '^' will
behave differently in 8-bit or 16-bit mode. You have to tell the assembler
what mode(s) with new pseudo-ops:
.sa ;set 8-bit memory/accumulator
.la ;set 16-bit memory/accumulator
.sx ;set 8-bit index registers
.lx ;set 16-bit index registers
You must also do the appropriate REP or SEP at the same time, because the .SA,
.LA, .SX, and .LX pseudo-ops are instructions for the assembler only; if you
do one without the other, your program will fail spectacularly when it reaches
that point in execution. It was mainly for this reason that LISA diverged from
the standard as far as it did (also, the standard is very wordy).
As an example, you could do this, if you really wanted to:
M = %00100000 ;memory/ACC bit
X = %00010000 ;index registers bit
TEST sep #M+X ;set for 8-bit ACC, index
.sa ;tell LISA about short ACC
.sx ;and short index
lda #$23 ;get a byte
sta ONEBYTE ;save a byte
rep #M ;set for 16-bit ACC
.la ;and long ACC now
lda #$23 ;get a word (#$0023)
sta TWOBYTE ;save a word
It looks straightforward here, but gets confusing in a 1,000 line program.
Also, there's a lot more typing to do to keep things straight.
So, what are the differences between LISA and the so-called standard? First,
there are various byte and word select operators for immediate values and
constants.
8-bit
LISA Standard Operation result
#$01020304 #$01020304 select low byte 04
or
#<$01020304
/$01020304 #>$01020304 select middle byte 03
^$01020304 #^$01020304 select high byte 02
16-bit
LISA Standard Operation result
|$01020304 #$01020304 select low word 0304
or or
#$01020304 #<$01020304
\$01020304 #^$01020304 select high word 0102
or
^$01020304
Further, LISA handles labels a little differently as well. In the 6502, there
were two different kinds of addresses P zero page and absolute. In the 65816,
there are three kinds P direct page (similar to zero page), absolute, and long.
The standard is to let the size of the address tell the type, so that addresses
from 0I255 are direct page, addresses from 256I65535 are absolute, and the rest
(65536I16777215) are long. LISA, on the other hand, uses explicit declarators
to set direct page, absolute, and long.
LISA Standard Operation
LABEL epz $23 LABEL equ $23 define direct-page label
LABEL equ $1234 LABEL equ $1234 define absolute label
LABEL eql $123456 LABEL equ $123456 define long label
LISA has different equates for two reasons; 1) it is more precise, and 2) it
handles conflicts with low-valued absolute and long addresses. This was not
as much a problem with the 6502 as it is with the 65816, with it's multiple
banks of 64K blocks (via the data and program bank registers) and moveable
direct page. Consider the following:
org $010000 ;start at bank $01, addr $0000
CODE lda #$02 ;set DBR to bank $02
pha
plb ;stack value to DBR
lda DATA
rts
DATA byt 5
DATA is in the same bank as CODE, so any reasonable assembler would say "Ah HA!
Same bank, so use absolute". Unfortunately, we set the DBR (data bank register)
to a different bank, so we end up fetching data from $02xxxx instead of $01xxxx.
This is where several LISA pseudo-ops come into play.
First, there is the FAR pseudo-op. If FAR is used on any label, it will
automatically be given the LONG attribute, and any instructions referencing
that will use long addressing modes. This way, you could do equates into
bank 0 that were long easily, without having to do type coercion for every
single reference (as the standard would have you do). For the case above,
we could have added:
far DATA ;declare DATA as long always
after the first line, and that would have made all references to DATA as long.
This is fine sometimes (and usually necessary if you're working with banks given
you by the IIGS memory manager), but what if you know what banks you're in at
certain times, and want to optimize some fetches that would be in the same bank.
Example
org $010000 ;start at bank $01, addr $0000
far DATA ;declare DATA as long always
far DATA2 ;declare DATA2 as long always
CODE lda #$02 ;set DBR to bank $02
pha
plb ;stack value to DBR
lda DATA ;will be LONG ref
sta DATA2 ;will be LONG ref
rts
DATA byt 5
org $020000 ;in bank $02
DATA2 byt 12
Now, here we are loading from $01xxxx and saving at $02xxxx; but we know that
the DBR is pointing at bank $02, and DATA2 is needlessly long. We could have
not defined DATA2 as FAR, but then later we might have moved the DBR again and
still want to have referenced DATA2 again. What to do?
To fix this, we introduce a new pseudo ops; .DB. The action of this is to
inform the assembler (if we know) what the value of the DBR register is. So,
looking at our example again, we could do the following:
org $010000 ;start at bank $01, addr $0000
CODE lda #$02 ;set DBR to bank $02
pha
plb ;stack value to DBR
.db $020000 ;tell LISA we're in bank $02
lda DATA ;will be LONG ref
sta DATA2 ;will be ABS ref
lda #$01 ;set DBR to bank $01
pha
plb ;stack value to DBR
.db $010000 ;tell LISA we're in bank $02
lda DATA ;will be ABS ref
sta DATA2 ;will be LONG ref
rts
DATA byt 5
org $020000 ;in bank $02
DATA2 byt 12
Now, as you can see from reading the comments, LISA will keep track of the
different segments, and generate ABSOLUTE or LONG instructions in context.
Effective use of the .DB pseudo-op can save you lots of trouble. NOTE: the
.DB instruction expects a long address, of which it uses the high-order byte
only. This is so that you can use a label in the bank you're targeting with
the .DB instruction; also, so that you can use the value of the PC counter,
i.e. ".DBJ*", which would reset LISA's notion of where the DBR is to the
current value of the PBR (program bank register).
One useful trick when setting the DBR to point to a bank returned by the
memory manager is to use .DB to tell LISA that you are in a bank which will
never be referenced normally, like bank $FF (which contains sancrosanct ROM
code on the IIGS). I.e.
lda SymBank ;set DBR to symbol table bank
pha
plb ;stack value to DBR
plb
.db $FF0000 ;tell LISA we're in bank $FF
lda (Sym) ;get symbol byte
sta DATA ;and save it
where here we load ABSOLUTE from a bank that we don't know the location of at
assembly timeP so we tell LISA it's at a location we will never access normally.
Note that you could NOT say ".DBJSYMBANK" because SYMBANK is a variable, not an
assembly-time constant.
Finally, there are every once in a while situations that don't fall into any
of the above catagories, where you want to have some reference be ABSOLUTE or
LONG where there is no way to tell the assembler other than by type coercion.
Since there are already so many prefix characters for an expression (byte, word,
negation etc), LISA prefers to use a suffix on the label P this also makes more
sense, since you are coercing the label, not the expression (although the
expression usually follows as a consequence). To do type coercion, you follow
the label with a suffix like ":A" for ABSOLUTE and ":L" for LONG. So, we could
do
lda SymBank ;set DBR to symbol table bank
pha
plb ;stack value to DBR
plb
lda BYTES:A ;absolute ref
sta DATA ;and save it
if we knew that DATA was an offset from the start of the bank, say if we had
allocated a whole 64K bank and were using absolute offsets into that bank
(which can be a handy thing to do at times).
So there are some more differences to categorize between LISA and the standard
65816 syntax. They are.
LISA Standard Operation
(not applicable) >Label force direct page
Label:A |Label force absolute
Label:L <Label force long
_______________________________________________________________________
Label epz $23 Label equ $23 force direct page
lda Label lda <Label
LISA needs no coercion Labels are rarely (if ever)
because the label is coerced like this anyway.
defined as direct page.
_______________________________________________________________________
Label equ $23 Label equ $23 force absolute
lda Label lda |Label
LISA needs no coercion This is needed to force
because the label is absolute for low addresses.
defined as absolute.
Label byt "a String"
lda Label:A,X
LISA can coerce labels if
it has to, though.
_______________________________________________________________________
Label eql $2305 Label equ $23 force long
lda Label lda >Label
LISA needs no coercion This is needed to force
because the label is long for low addresses.
defined as long.
Label byt "a String"
lda Label:L,X
LISA can coerce labels if
it hasto, though.
_______________________________________________________________________
Postscript:
As you may have inferred, this is taken from the manual for the LISA816
assembler.
I have translated it to text and downloaded it for a specific reason:
evangelization. There are some quirky things about the "standard" way of
doing things that I disagree with; some decisions were made without being
thought through. This is my opinion, of course. If you have rational
comments or objections, I would be more than delighted to hear from you. I
may be contacted on
GEnie: HAL.LABS
Delphi HALLABS
CompuServe: 72250,3226
or (through the vagaries of the US mail)
HAL Labs
18942 Dallas
Perris, CA 92370
Address comments to Brian Fitzgerald.
The LISA 816 assembler package is currently at version 4.0g, and may be
purchased from the above address for $50.00. It currently runs in emulation
mode, but generates OMF files (as well as BIN etc) that can be run in native
mode. Native-mode LISA is scheduled for a Xmas release, and will be $75.00
(or a $25.00 upgrade from 4.0). LISA816 boasts 100,000 lines per minute
assembly, interactive syntax-checking editor, fully panoply of data-declaration